﻿using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RPGProject.GameWorld.GameObjects;
using RPGProject.Data_Structures;
using RPGProject.LoopSystem;
namespace RPGProject.GameWorld
{
    sealed partial class World : Loop, IGOEventListener
    {
        /// <summary>
        /// Listens to an event.
        /// </summary>
        /// <param name="goEvent">The event to listen to.</param>
        /// <returns>An event if needed.</returns>
        public GameObjectEvent Listen(GameObjectEvent goEvent)
        {
            // logs all events
            this.LogEvent(goEvent);
            foreach (ScriptRunner sr in this.scripts)
            {
                sr.Listen(goEvent);
            }
            // important variable declarations
            GameObjectEvent ret;
            GameObject go;
            GameObject source;
            GameObject target;
            int xmod;
            int ymod;
            int sx;
            int sy;
            switch (goEvent.Name)
            {
                case "win":
                    this.loopManager.ClearAllFuncs();
                    this.visuals.ToShow = VisualType.WIN;
                    this.loopManager.AddFunc(FuncType.ADD_TO_FRONT, this.visuals);
                    this["end"] = true;
                    break;
                case "lose":
                    this.loopManager.ClearAllFuncs();
                    this.visuals.ToShow = VisualType.LOSE;
                    this.loopManager.AddFunc(FuncType.ADD_TO_FRONT, this.visuals);
                    this["end"] = true;
                    break;
                case "death":
                    // kills the GameObject that is "source"
                    source = (GameObject)goEvent["source"];
                    this.RemoveGameObject(source);
                    break;
                case "move":

                    // swaps the source and target GameObjects
                    source = (GameObject)goEvent["source"];
                    target = (GameObject)goEvent["target"];
                    this.SwapActiveLevel((int)source["x"], (int)source["y"], (int)target["x"], (int)target["y"]);

                    break;
                case "attackAttempt":
                    // processes the attackAttempt if it doesn't have a target
                    // but only a location
                    if (goEvent.Has("target"))
                    {
                        target = (GameObject)goEvent["target"];
                        target.Listen(goEvent);
                    }
                    else if (goEvent.Has("xmod") && goEvent.Has("ymod"))
                    {
                        source = (GameObject)goEvent["source"];
                        sx = (int)source["x"];
                        sy = (int)source["y"];
                        xmod = (int)goEvent["xmod"];
                        ymod = (int)goEvent["ymod"];
                        target = this.activeLevel[sx + xmod][sy + ymod];
                        target.Listen(goEvent);
                    }
                    break;
                case "moveAttempt":
                    // move to character class or gameobject class
                    // tells the target at (sx + xmod, sy + ymod) to listen to the moveAttempt
                    source = (GameObject)goEvent["source"];
                    sx = (int)source["x"];
                    sy = (int)source["y"];
                    xmod = (int)goEvent["xmod"];
                    ymod = (int)goEvent["ymod"];
                    target = this.activeLevel[sx + xmod][sy + ymod];
                    goEvent["target"] = target;
                    ret = target.Listen(goEvent);
                    if (ret != null)
                        this.Listen(ret);
                    break;
                case "remove":
                    source = (GameObject)goEvent["source"];
                    sx = (int)source["x"];
                    sy = (int)source["y"];
                    this.RemoveGameObject(source);
                    break;
                case "spawn":
                    // spawns something of type specified
                    String spawn = (String)goEvent["spawnpoint"];
                    String type = (String)goEvent["type"];
                    String name = (String)goEvent["name"];
                    GameObject toSpawn = null;
                    switch (type)
                    {
                        case "player":
                            toSpawn = new Player(0, 0, name, this);
                            break;
                        case "enemy":
                            toSpawn = new Enemy(0, 0, name, this);
                            break;
                        default:
                            toSpawn = (GameObject)Activator.CreateInstance(Assembly.GetExecutingAssembly().GetType("RPGProject.GameWorld.GameObjects." + type, true, true), new object[] { 0, 0, name, this });
                            break;
                    }
                    if (this.SpawnCharacter(toSpawn, spawn))
                    {
                        switch (type)
                        {
                            case "player":
                                PlayerLoop pl = new PlayerLoop(this.visuals, this.loopManager, this);
                                this.loopManager.AddFunc(FuncType.ADD_TO_FRONT, pl);
                                break;
                            case "enemy":
                                EnemyLoop el = new EnemyLoop(this.loopManager, (Enemy)toSpawn);
                                this.loopManager.AddFunc(FuncType.ADD_TO_FRONT, el);
                                break;
                        }
                    }
                    break;
                case "enemyturn":
                    // tells each enemy to take its turn
                    foreach (GameObject testEnemy in this.activeObjects.Values)
                    {
                        if (testEnemy is Enemy)
                            testEnemy.Listen(goEvent);
                    }
                    break;
                case "loadmap":
                    // load the next map
                    String nextMap = (String)goEvent["map"];
                    this.Reload(nextMap);
                    GameObject temp = this.activeObjects["player"];
                    this.activeObjects.Clear();
                    this.activeObjects["player"] = temp;
                    this.Listen(new GameObjectEvent("reinit"));
                    break;
                case "relocate":
                    if (goEvent.Has("source"))
                    {
                        source = (GameObject)goEvent["source"];
                    }
                    else
                    {
                        String key = (String)goEvent["name"];
                        source = this.activeObjects[key];
                    }
                    sx = (int)source["x"];
                    sy = (int)source["y"];
                    if (goEvent.Has("x"))
                    {
                        source["x"] = (int)goEvent["x"];
                        source["y"] = (int)goEvent["y"];
                    }
                    else
                    {
                        SpawnPoint sp = this.spawnPoints[(String)goEvent["target"]];
                        source["x"] = sp.x;
                        source["y"] = sp.y;
                    }
                    this.PutGameObject(source);
                    this.PutGameObject(new NullGameObject(sx, sy));
                    break;
                default:
                    // delegates events towards the sources and the targets
                    if (goEvent.Has("source"))
                    {
                        go = (GameObject)goEvent["source"];
                        go.Listen(goEvent);
                    }
                    if (goEvent.Has("target"))
                    {
                        go = (GameObject)goEvent["target"];
                        go.Listen(goEvent);
                    }
                    break;
            }
            return null;
        }
        /// <summary>
        /// Sends out an init event, and sets turns to 1.
        /// </summary>
        public void Init()
        {
            this.Listen(new GameObjectEvent("init"));
            this["turn"] = 1;
            this.Render(true);
        }
        /// <summary>
        /// Does a turn in gametime.
        /// </summary>
        /// <param name="forceRender">Forces the world to rerender.</param>
        public void Update(bool forceRender = false)
        {
            // increments turn

            this["turn"] = (int)this["turn"] + 1;
            // frame event
            GameObjectEvent turn = new GameObjectEvent("turn");
            turn["turn"] = this["turn"];
            this.Listen(turn);
            // Gets a list of all the non-null and non-wall listeners.
            Vector<IGOEventListener> listeners = new Vector<IGOEventListener>();
            for (int i = 0; i < this.activeLevel.Length; i++)
            {
                for (int j = 0; j < this.activeLevel[i].Length; j++)
                {
                    GameObject listen = this.activeLevel[i][j];
                    if (listen.TypeId != "null" || listen.TypeId != "wall")
                        listeners.Add(listen);
                }

            }
            GameObjectEvent ret;

            // sends the frame update
            // if it returns an event, the world listens to it.
            foreach (IGOEventListener listener in listeners)
            {
                ret = listener.Listen(turn);
                if (ret != null)
                    this.Listen(ret);
            }
            this.Render(forceRender);

        }
        /// <summary>
        /// Logs the GameObjectEvent passed.  If its a certain type of event, the parameters will be formatted.
        /// </summary>
        /// <param name="goEvent">The event to be logged.</param>
        public void LogEvent(GameObjectEvent goEvent)
        {
            String eventString = "";
            switch (goEvent.Name)
            {
                case "enemyturn":
                    eventString = "The enemies' turn has begun!";
                    break;
                case "playerturn":
                    eventString = "Your turn has begun!";
                    break;
                case "kill":
                    eventString = goEvent["source"] + " has killed " + goEvent["target"] + ".";
                    break;
                case "killAttempt":
                    eventString = goEvent["source"] + " attempts to kill " + goEvent["target"] + ".";
                    break;
                case "death":
                    eventString = goEvent["source"] + " has died.";
                    break;
                case "damage":
                    eventString = goEvent["source"] + " hit " + goEvent["target"] + "!";
                    break;
                case "miss":
                    eventString = goEvent["source"] + " missed " + goEvent["target"] + "!";
                    break;
                case "spawn":
                    eventString = goEvent["name"] + " of type " + goEvent["type"] + " spawned at spawn point " + goEvent["spawnpoint"];
                    break;
                case "exception":
                    eventString = goEvent.ToString();
                    break;
                default:
                    eventString = goEvent.ToString();
                    // uncomment the below line to have it log only events that have formats available
                    return;
            }
            this.smallLogBuffer.WriteLine(eventString);
            this.bigLogBuffer.WriteLine(eventString);
        }

        public override FuncType RunOnce()
        {
            this.Update(false);
            if((bool)this["end"] == true)
                return FuncType.REPEAT;
            else
                return FuncType.ADD_TO_END;
        }
    }
}
